-------------------------------------------------------------------------------
-- RTSAI - Real-Time Strategy engine for AI research
-- Copyright(C) 2006 DIMAp-UFRN, licensed under LGPL
--
-- Control functions in Lua.
-------------------------------------------------------------------------------

package.path = "./?.lua;./scripts/?.lua"

--=============================================================================
-- Table initializations. Set up default values.
--=============================================================================

math.randomseed(os.time())

-- Function to register an event in the game.
function createEvent ( event_name, check_function )
    local event_class = { }

    -- the c++ virtual function "check" will consult the given "check_function"
    -- and if it returns a table (i.e. the event happened), the table is converted
    -- in GameEventData.
    event_class.check = function ( self )
        local returned_table = check_function()
        if returned_table then
            local event_data = rtsai.GameEventData:new()
            tolua.setpeer ( event_data, returned_table )
            return event_data
        end
    end

    event_class.__index = event_class
    local event_peer_table = {}
    setmetatable ( event_peer_table, event_class )

    -- when an event is created, it is registered in the game
    local event = rtsai.Lua__GameEvent:new ( event_name )
    tolua.setpeer ( event, event_peer_table )

    event:tolua__set_instance ( event )
end

-- global table with the registered actions
action = { }

-- Function to register an action in the game.
function createAction ( action_name, precondition_function, execute_function )
    local action_table = {
        name = action_name,
        check_precondition = precondition_function,
        execute = execute_function
    }

    action_table.__index = action_table
    local peer = { value = nil }
    setmetatable ( peer, action_table )

    local instance = rtsai.Lua__Action:new ( )
    tolua.setpeer ( instance, peer )
    instance:tolua__set_instance ( instance )

    action [ action_name ] = instance
    instance.null = 0 -- just to force an instance to have attibutes
end

-------------------------------------------------------------------------------
-- Game table initialization. Set up default values.
--
function Game ( self )

    if not self.init then error ("There is no game initialization.\nPlease, define the 'init' function in your game") end
    if not self.players then error ("There is no player specified in the game.\nPlease, define the 'players' attribute in your game") end
    if #self.players > 4 then error ("Only four players or less supported") end

    game = rtsai.Game:getInstance()
    game.visibility = self.visibility or "GLOBAL"

    game.loop_callback = self.loop_callback or function() end

    -- registering the set of possible events in the game
    for event_name, cond in pairs ( self.events or {} ) do
        createEvent ( event_name, cond )
    end

    -- registering the set of possible actions in the game
    for action_name, action_table in pairs ( self.actions ) do
        if not action_table.execute then error ("There is no execute function in the action <"..action_name..">") end
        local empty_function = function() return true end
        precond = action_table.precondition or empty_function
        createAction ( action_name, precond, action_table.execute)
    end


    -- setting up players in the game
    game.players = {}
    for name, values in pairs (self.players) do
        local p = game:create_player()
        setupPlayer(values, name, p)
        game.players[name] = p
    end

    -- registering the object types in the game
    game.objects = {}
    for name, values in pairs (self.objects) do
        game.objects[name] = values
        if values.type == "circ" then
            game.objects[name].new = function ( self, pos )
                    local obj = rtsai.SphObject:new ( name, self.radius, pos, self.obstacle )
                    game : add_obj ( obj )
                    return obj
                end
        elseif values.type == "rect" then
            game.objects[name].new = function ( self, pos )
                    local obj = rtsai.RectObject:new ( self.width, self.height, pos, self.obstacle )
                    game : add_obj ( obj )
                    return obj
                end
        end
    end

    self.init ( game )
end


function setupPlayer (values, name, player)
    if not values.civilization then error("Player civilization not specified.\nPlease, define the 'civilization' attribute for your player <"..self.name..">") end
    if not values.strategy then error("Player strategy not specified.\nPlease, define the 'strategy' attribute for your player <"..self.name..">") end

    require(values.civilization)
    require(values.strategy)

    player:set_name(name)
    player:set_civ_name(values.civilization)

    player.createUnit = function(self, class, state)
        if not class then error("Unit class not defined when creating a unit") end
        if not state then error("Initial state not defined when creating a unit") end

        local unit = self:create_unit()
        tolua.setpeer ( unit, class:new() )

        if type(state) == "string" then state = _G[state] end
        unit:change_state ( state:new() )

        return unit
    end
end


--=============================================================================
-- A generic class, from where other class will inherit.
--=============================================================================

__Class = {}   -- the prefixe '__' is to assure nobody is using the same name

-------------------------------------------------------------------------------
-- Defines __Class as a metatable for the table being created
function __Class:new(s)
    s = s or {}  -- create a new table if it was not provided one
    setmetatable(s, self) -- By setting self as metatable, when a field is not
    self.__index = self   -- found in s, lua will look for this field in self.
    return s              -- Thus, everything in self is "inherited" to s.
end

-------------------------------------------------------------------------------
-- Creates a table inherited from __Class or from the parent (which must be
-- subclass of __Class).
-- The object's name is put in the global environment, and so it can be
-- accessed as a global variable.
function createObj(self, parent)
    if not self.name then error("object creation without a name") end

    if parent then
        _G[self.name] = parent:new(self)
    else
        _G[self.name] = __Class:new(self)
    end
end


--=============================================================================
-- A class representing a state of a unit. It is use to set up the behavior of
-- a unit as state machine.
--=============================================================================

-------------------------------------------------------------------------------
-- State table initialization. At least the field name must be defined.
-- The state's name will be a global variable.
-- Ex:
--   State {
--      name = "my_state"
--   }
function State(self)
    if not self.name then error ("State name not defined") end
    if _G[self.name] then print ("Warning: State <"..self.name.."> already exists") end

    if self.extends ~= nil then
        _G[self.name] = self.extends : new ( self )
    else
        _G[self.name] = createState ( self )
    end
end

function createState (self)
    local self = self or {}
    self.__index = self

    self.new = function ( self, attr_table )
        local s = attr_table or {}

        if type(self) ~= "table" and tolua.type(self) == "Lua__UnitState" then
            self = tolua.getpeer ( self )
            self.__index = self
        end

        for k, v in pairs ( self ) do
            if type (v) ~= "function" and k ~= "name" and k ~= "events" then
                s [ k ] = v
            end
        end

        setmetatable(s, self)
        local state = rtsai.Lua__UnitState:new()
        tolua.setpeer(state, s)
        state:tolua__set_instance(state)

        for _, event in pairs ( self.events ) do
            state:listen_to ( event )
        end

        return state
    end

    self.clone = function ( self, attr_table )
        local s = attr_table or {}

        print (tolua.type(self))
        if type(self) ~= "table" and tolua.type(self) == "Lua__UnitState" then
            print ( "Creation of a state from another state" )
            self = tolua.getpeer ( self )
            self.__index = self
        end
        print ( s.act )
        print ( self.act )

        for k, v in pairs ( self ) do
            if type (v) ~= "function" and k ~= "name" and k ~= "events" then
                s [ k ] = v
            end
        end

        setmetatable(s, self)
        local state = rtsai.Lua__UnitState:new()
        tolua.setpeer(state, s)
        state:tolua__set_instance(state)

        for _, event in pairs ( self.events ) do
            state:listen_to ( event )
        end

        return state
    end


    self.events = {}
    for k,v in pairs(self) do
        if string.find(k, "on_") and k ~= "on_enter" and k ~= "on_exit" then
            local event_name = string.gsub ( k, "on_", "", 1)
            local event = game:get_event ( event_name )
            self.events[event_name] = event
        end
    end

    self.react_to_event = function ( self, event_name, event_data )
        local event_call = self [ "on_" .. event_name ]
        if type ( event_call ) == "function" then
            return event_call ( self, event_data )
        end
    end

    return self
end



--=============================================================================
-- A class representing a class of a unit. The attributes of a unit class is
-- inherit to the unit. For instance, is a unit class has the attribute 'force'
-- units from this class has also a 'force' attribute. In other words, the
-- unit class defines the stereotype of a unit.
-- The way in which a unit class is defined follows the sintaxe:
--   UnitClass {
--       name = "unit_class_name",
--       attr_1 = default_value,
--       attr_2 = default_value,
--       ...
--   }
--=============================================================================

-- This is the default class of a unit. All other classes will inherit from it.
baseClass =  __Class:new()
baseClass.name = "baseClass"  -- Again, the name is for compatibility reasons.

-------------------------------------------------------------------------------
-- Unit class table initialization. At least the field name must be defined.
-- The class' name will be a global variable.
-- Ex:
--   UnitClass {
--      name = "archer",
--      precision = 10,
--      ...
--   }
function UnitClass(self)
    if not civilization then error("You are trying to create a unit class without defining the civilization.\nPlease, specify the variable 'civilization' in your file.") end

    _G[civilization] = _G[civilization] or {}
    self.civilization = civilization

    if _G[civilization][self.name] then
        error("Unit class <"..civilization.."."..self.name.."> already defined.")
    end
    if self.parent then
        if _G[civilization][self.parent] then
             _G[civilization][self.name] = _G[civilization][self.parent]:new(self)
        else
            error("Parent class <"..civilization.."."..self.parent.."> is not defined.")
        end
    else
        _G[civilization][self.name] = baseClass:new(self)
    end
end



-------------------------------------------------------------------------------

function rect(w,h,o) return { type="rect", width=w, height=h, obstacle=o } end
function circ(r,o)   return { type="circ", radius=r, obstacle=o } end


-------------------------------------------------------------------------------

ERROR = 0.001
function isVectorNull ( v )
    return ( math.abs(v.x) < ERROR and math.abs(v.y) < ERROR and math.abs(v.z) < ERROR )
end


-------------------------------------------------------------------------------
-- LOG functions
-------------------------------------------------------------------------------

log = false -- if true, debug messages will appear

function log_bool ( msg, b )
    if log and b then
        print ( msg )
    end
end

function log_value ( msg, v )
    if log then
        print ( msg .. " = " .. v )
    end
end

function log_vector ( msg, v )
    if log then
        print ( msg .. " = ("..v.x..", "..v.y..", "..v.z..")")
    end
end

function log_unit ( unit )
    if log then
        log_vector ( "Unit position", unit:position() )
        log_vector ( "Unit velocity", unit:velocity() )
        log_vector ( "Unit forward", unit:forward() )
        log_value ( "Unit radius" , unit:radius() )
        log_value ( "Unit speed" , unit:speed() )

    end
end

function log_obstacle ( obst )
    if log then
        local c = obst:position()
        log_vector ("Obstacle center", obst:position() )
        log_value ("Obstacle radius", 2)
    end
end

function log_predict_collision ( unit, obst )
    if log then
        print ("PREDICT COLLISION")
        log_value ("Elapsed time", game.elapsed_time)
        log_unit ( unit )
        log_obstacle ( obst )
    end
end




